package com.example.shop.user.websocket;

import com.example.shop.common.enums.SessionAttributeKey;
import com.example.shop.common.enums.WebSocketUserPropertiesKey;
import com.example.shop.redis.enums.RedisKey;
import com.example.shop.redis.service.LettuceClient;
import com.example.shop.common.config.InformWebSocketConfigurator;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.elasticsearch.common.collect.CopyOnWriteHashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * 1.spring默认是单例模式，但是websocket却是多个客户端对一个服务端，所以spring容器不会对websocket进行注入
 * 2.建立连接时，会创建一个httpSession,这个session与该客户端相关联，与浏览器请求的session是不同的，是2个session
 */
@Component
//设置websocket请求的url路径，以及websocket服务器的配置类
@ServerEndpoint(value = "/websocket/inform/user/{key}",configurator = InformWebSocketConfigurator.class)
public class InformWebSocketServer {

    private static int onlineCount = 0;
    private static CopyOnWriteHashMap<String, InformWebSocketServer> webSocketMap = new CopyOnWriteHashMap<>();
    private Session session;
    private static ObjectMapper objectMapper;
    private static LettuceClient redisClient;
    private static RedisIndexedSessionRepository sessionRepository;

    @Autowired
    public void setRedisClient(LettuceClient redisClient) {
        if(InformWebSocketServer.redisClient == null){
            InformWebSocketServer.redisClient = redisClient;
        }
    }

    @Autowired
    public void setObjectMapper(@Qualifier("commonObjectMapper") ObjectMapper objectMapper) {
        if(InformWebSocketServer.objectMapper == null){
            InformWebSocketServer.objectMapper = objectMapper;
        }
    }

    @Autowired
    public void setSessionRepository(RedisIndexedSessionRepository sessionRepository) {
        if(InformWebSocketServer.sessionRepository == null){
            InformWebSocketServer.sessionRepository = sessionRepository;
        }
    }

    /**
     * 根据websocket的session会话id来获取该WebSocketServer
     * @param sessionId websocket的session会话id
     * @return 返回WebSocketServer对象
     */
    public static InformWebSocketServer getWebSocketServer(String sessionId){
        return webSocketMap.get(sessionId);
    }

    @OnOpen
    public void onOpen(Session session,@PathParam("key") String key) throws IOException {
        Map<String, Object> userProperties = session.getUserProperties();
        //获取websocket的ip
        String connIp = (String) userProperties.get(WebSocketUserPropertiesKey.CLIENT_IP.getKey());
        //获取请求令牌时关联的HttpSession的id
        String reqTokenHttpSessionId = (String) redisClient.get(RedisKey.USER_WEBSOCKET_TOKEN_REQ_SESSION + key);
        //如果从redis中获取不到websocket令牌，说明没有先去请求获取令牌
        if(reqTokenHttpSessionId == null){
            session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT,"不存在该令牌，请先去请求websocket令牌"));
            return;
        }
        //获取请求令牌时关联的HttpSession
        org.springframework.session.Session reqTokenHttpSession =  sessionRepository.findById(reqTokenHttpSessionId);
        //判断请求令牌时的ip是否与当前要建立websocket的ip一致
        String reqIp = reqTokenHttpSession.getAttribute(SessionAttributeKey.CLIENT_IP.getKey());
        if(!connIp.equals(reqIp)){
            session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT,"请求websocket令牌与建立连接不是同一端"));
            return;
        }
        //符合建立连接，删除websocket令牌
        redisClient.delKey(RedisKey.USER_WEBSOCKET_TOKEN_REQ_SESSION + key);
        //将请求令牌的HttpSession关联该会话，注意：分布式session的flushMode配置，要么IMMEDIATE，要么sessionRepository.save手动保存
        reqTokenHttpSession.setAttribute(SessionAttributeKey.INFORM_SESSION_ID.getKey(), session.getId());
        //设置二进制消息最大1M
        session.setMaxBinaryMessageBufferSize(8*1024*1024);
        //设置此会话的空闲超时时间。毫秒为单位。零或负值表示无限超时。
        session.setMaxIdleTimeout(1000*60*3);
        //设置文本消息，最大8*1024个字符
        session.setMaxTextMessageBufferSize(8*1024);
        this.session = session;
        webSocketMap = webSocketMap.copyAndPut(session.getId(),this);
        incrOnlineCount();
    }

    /**
     * 接收客户端发送过来的消息
     * @param message json消息
     * @param session tcp连接会话
     */
    @OnMessage
    public void acceptMessage(String message,Session session){
        System.out.println("前端会话sessionId:"+session.getId()+" 消息："+message);
    }

    /**
     * 此时已经关闭session，即session为null
     */
    @OnClose
    public void onClose(){
        Set<Map.Entry<String, InformWebSocketServer>> entrySet = webSocketMap.entrySet();
        Iterator<Map.Entry<String, InformWebSocketServer>> iterator = entrySet.iterator();
        while(iterator.hasNext()){
            Map.Entry<String, InformWebSocketServer> next = iterator.next();
            if(next.getValue().equals(this)){
                webSocketMap = webSocketMap.copyAndRemove(next.getKey());
                break;
            }
        }
        decrOnlineCount();
    }
    @OnError
    public void onError(Session session,Throwable error){
        System.out.println("出错："+session.getId());
        error.printStackTrace();
    }

    public void sendByteArray(byte[] bytes,boolean isLast) throws IOException {
        RemoteEndpoint.Basic basicRemote = session.getBasicRemote();
        ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length);
        byteBuffer.put(bytes);
        //发送一个二进制数据，如果isLast为false，则代表发送的数据还未完成(在此过程中，不能发送其他数据，比如：文本等),只能直到isLast为true，才可以发送其他数据
        basicRemote.sendBinary(byteBuffer,isLast);
    }

    public void sendObject(Object obj) throws EncodeException, IOException {
        RemoteEndpoint.Basic basicRemote = session.getBasicRemote();
        //省了写转换
//        basicRemote.sendObject(obj);
        basicRemote.sendText(objectMapper.writeValueAsString(obj));
    }

    public void sendMessage(String message) throws IOException, EncodeException {
        RemoteEndpoint.Basic basicRemote = session.getBasicRemote();
        basicRemote.sendText(message);
    }

    synchronized public static  void incrOnlineCount(){
        onlineCount++;
    }

    synchronized public static  void decrOnlineCount(){
        onlineCount--;
    }
}
